﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NUnit.Framework;

namespace SwormTests.Joins
{
    public class simple_many_to_many : TestBase
    {
        public simple_many_to_many(DbToTest type) : base(type) { }

        class users
        {
            [Sworm.Column(IsPrimaryKey = true, IsAutoIncrement = false)]
            public long id { get; set; }
            public string name { get; set; }
            public List<users_to_groups> mappings { get; set; }
            public IEnumerable<groups> groups { get { return mappings.Select(m => m.group); } }
        }

        class users_to_groups
        {
            [Sworm.Column(IsPrimaryKey = true, IsAutoIncrement = false)]
            public long id { get; set; }
            public long user_id { get; set; } // "MemberName_Id" (with underscore) is supported by convention...
            public users user { get; set; }
            public long groupid { get; set; } // ... as well as "MemberNameId" (without underscore)
            public groups group { get; set; }
        }

        class groups
        {
            [Sworm.Column(IsPrimaryKey = true, IsAutoIncrement = false)]
            public long id { get; set; }
            public string name { get; set; }
            public List<users_to_groups> mappings { get; set; }
            public IEnumerable<users> users { get { return mappings.Select(m => m.user); } }
        }

        users bob;
        users alice;
        users carl;

        groups g1;
        groups g2;
        groups g3;

        protected override void Initialize()
        {
            bob = InsertUser("bob");
            alice = InsertUser("alice");
            carl = InsertUser("carl");

            g1 = InsertGroup("g1");
            g2 = InsertGroup("g2");
            g3 = InsertGroup("g3");
        }

        int id = 1;

        users InsertUser(string name)
        {
            var u = new users() { id = id++, name = name };
            DB.Insert(u);
            return u;
        }

        groups InsertGroup(string name)
        {
            var g = new groups() { id = id++, name = name };
            DB.Insert(g);
            return g;
        }

        void Map(users user, params groups[] groups)
        {
            foreach (var group in groups)
            {
                var mapping = new users_to_groups() { id = id++, user_id = user.id, groupid = group.id };
                DB.Insert(mapping);
            }
        }

        [Test]
        public void dont_join()
        {
            var users = DB.Query.Joining<users>("u").List();
            Assert.AreEqual(3, users.Count);

            var groups = DB.Query.Joining<groups>("g").List();
            Assert.AreEqual(3, groups.Count);
        }

        [Test]
        public void todo902()
        {
            Assert.Inconclusive("TODO Include() and Exclude() methods");

            // for example
            DB.Query.Joining<users>("u")
                .JoinLeft(u => u.mappings, "map")
                .JoinLeft("map", (users_to_groups map) => map.group, "g")
                .Include("u.*", "g.name")
                .Include("u.*, g.name") // this looks a little better, no?
                .List();
        }

        [Test]
        public void join1()
        {
            // setup
            Map(alice, g1, g2);
            Map(bob, g1, g2, g3);

            // test using string-path style
            var users = DB.Query.Joining<users>("u")
                .JoinLeft("u.mappings", "m")
                .JoinLeft("m.group", "g")
                .List();

            join1_assertions(users);

            // test using expression-path style
            users = DB.Query.Joining<users>("u")
                .JoinLeft(u => u.mappings, "m")
                .JoinLeft("m", (users_to_groups m) => m.group, "g")
                .List();

            join1_assertions(users);

            // Same as before, just avoid the convenience overload for the root join.
            // More verbose, but it kind of looks nicer, more consistent.
            users = DB.Query.Joining<users>("u")
                .JoinLeft("u", (users u) => u.mappings, "m")
                .JoinLeft("m", (users_to_groups m) => m.group, "g")
                .List();

            join1_assertions(users);
        }

        private void join1_assertions(IList<users> fromDB)
        {
            // find entities amongst results
            alice = fromDB.Where(u => u.id == alice.id).Single();
            bob = fromDB.Where(u => u.id == bob.id).Single();
            carl = fromDB.Where(u => u.id == carl.id).Single();

            g1 = fromDB.SelectMany(u => u.groups).Distinct().Single(g => g.id == g1.id);
            g2 = fromDB.SelectMany(u => u.groups).Distinct().Single(g => g.id == g2.id);
            g3 = fromDB.SelectMany(u => u.groups).Distinct().Single(g => g.id == g3.id);

            // make assertions
            Assert.AreEqual(2, alice.mappings.Count);
            Assert.AreEqual(2, alice.groups.Count());

            Assert.AreEqual(3, bob.mappings.Count);
            Assert.AreEqual(3, bob.groups.Count());

            Assert.AreEqual(0, carl.mappings.Count);
            Assert.AreEqual(0, carl.groups.Count());

            Assert.AreEqual(2, g1.users.Count());
            Assert.IsTrue(g1.users.Contains(alice));
            Assert.IsTrue(g1.users.Contains(bob));
            Assert.IsFalse(g1.users.Contains(carl));
        }

        [Test]
        [ExpectedException(typeof(Sworm.InvalidQueryException))]
        public void circular_joins_are_not_allowed()
        {
            try
            {
                var users = DB.Query.Joining<users>("u")
                    .JoinLeft("u", (users u) => u.mappings, "m")
                    .JoinLeft("m", (users_to_groups m) => m.user, "userAgain") // mistake?
                    .List();
            }
            catch (Sworm.InvalidQueryException ex)
            {
                Assert.AreEqual("circular join detected", ex.Message);
                throw;
            }
        }

        [Test]
        public void with_where_clause()
        {
            Map(alice, g1, g3);
            Map(bob, g3);

            // filter on Group g3
            var users = DB.Query.Joining<users>("u")
                .JoinLeft(u => u.mappings, "m")
                .JoinLeft("m", (users_to_groups m) => m.group, "g")
                .Where(@"g.id = @0", g3.id)
                .List();

            Assert.AreEqual(2, users.Count);
            Assert.AreSame(users[0].groups.Single(), users[1].groups.Single());

            // filter on User alice and Group g3
            users = DB.Query.Joining<users>("u")
                .JoinLeft(u => u.mappings, "m")
                .JoinLeft("m", (users_to_groups m) => m.group, "g")
                .Where(@"g.id = @0", g3.id)
                .Where(@"u.name = @0", "alice")
                .List();

            Assert.AreEqual(1, users.Count);
            alice = users.Single();
            Assert.AreEqual("alice", alice.name);
            Assert.AreEqual(1, alice.groups.Count());
        }

        [Test]
        public void with_order_clause()
        {
            var users = DB.Query.Joining<users>("u")
                .OrderBy(@"u.name")
                .List();

            Assert.AreEqual("alice", users[0].name);
            Assert.AreEqual("bob", users[1].name);
            Assert.AreEqual("carl", users[2].name);

            users = DB.Query.Joining<users>("u")
                .OrderBy(@"u.name desc")
                .List();

            Assert.AreEqual("carl", users[0].name);
            Assert.AreEqual("bob", users[1].name);
            Assert.AreEqual("alice", users[2].name);
        }

        [Test]
        public void todo235()
        {
            Assert.Inconclusive("TODO allow overloads that take the alias and the table/view name");
        }
    }
}
